﻿using System;
using System.Collections.Generic;
using System.Data.EntityClient;
using System.Data.Objects.SqlClient;
using System.Linq;
using System.Linq.Expressions;
using System.Web;
using AlmaDatabase;

namespace Alma.Core
{
    class SearchLinqBuilder : ISearchExpressionProcessor
    {
        private class ParameterReplacer : ExpressionVisitor
        {
            private readonly ParameterExpression _source;
            private readonly ParameterExpression _dest;

            public ParameterReplacer(ParameterExpression source, ParameterExpression dest)
            {
                _source = source;
                _dest = dest;
            }

            protected override Expression VisitParameter(ParameterExpression node)
            {
                if (node == _source)
                    return _dest;
                return base.VisitParameter(node);
            }
        }

        private Stack<Expression> _results = new Stack<Expression>();
        private ParameterExpression _target = null;
        private Expression<Func<Product, bool>> _result;
        private AlmaDBEntities _entities;

        public SearchLinqBuilder(AlmaDBEntities entities)
        {
            _entities = entities;
        }

        public void Process(SearchAnd ex)
        {
            ex.Right.Accept(this);
            ex.Left.Accept(this);
            _results.Push(Expression.AndAlso(_results.Pop(), _results.Pop()));
        }

        public void Process(SearchOr ex)
        {
            ex.Right.Accept(this);
            ex.Left.Accept(this);
            _results.Push(Expression.OrElse(_results.Pop(), _results.Pop()));
        }

        public void Process(SearchNot ex)
        {
            ex.Inner.Accept(this);
            _results.Push(Expression.Not(_results.Pop()));
        }

        public void Process(SearchWord ex)
        {
            _results.Push(Expression.Call(Expression.Property(_target, "Name"), typeof(string).GetMethod("Contains"), Expression.Constant(ex.Value)));
        }

        public void Process(SearchSelector ex)
        {
            Expression<Func<Product, bool>> expr;
            switch (ex.Selector)
            {
                case "desc":
                    expr = x => x.Description.Contains(ex.Value);
                    break;
                case "cat":
                    expr = x => x.Sections.Categories.Name.Contains(ex.Value);
                    break;
                case "sec":
                    expr = x => x.Sections.Name.Contains(ex.Value);
                    break;
                default:
                    throw new SearchException("Sélecteur invalide '" + ex.Selector + "'");
            }

            var replacer = new ParameterReplacer(expr.Parameters[0], _target);
            var newBody = replacer.Visit(expr.Body);
            _results.Push(newBody);

        }

        public void Process(SearchCriterion ex)
        {
            Expression<Func<Product, bool>> expr;
            float dVal;
            try
            {
                switch (ex.Operator)
                {
                    case "=":
                        dVal = float.Parse(ex.Value);
                        expr = (x => x.Product_Criterion.FirstOrDefault(y => y.Criterion.Name == ex.Criterion) != null
                                     &&
                                     Functions.StringToFloat(
                                         x.Product_Criterion.FirstOrDefault(y => y.Criterion.Name == ex.Criterion).Value) ==
                                     dVal);
                        break;
                    case ">":

                        dVal = float.Parse(ex.Value);
                        expr = (x => x.Product_Criterion.FirstOrDefault(y => y.Criterion.Name == ex.Criterion) != null
                                     &&
                                     Functions.StringToFloat(
                                         x.Product_Criterion.FirstOrDefault(y => y.Criterion.Name == ex.Criterion).Value) >
                                     dVal);
                        break;
                    case "<":

                        dVal = float.Parse(ex.Value);
                        expr = (x => x.Product_Criterion.FirstOrDefault(y => y.Criterion.Name == ex.Criterion) != null
                                     &&
                                     Functions.StringToFloat(
                                         x.Product_Criterion.FirstOrDefault(y => y.Criterion.Name == ex.Criterion).Value) <
                                     dVal);
                        break;
                    case "#":
                        expr = (x => x.Product_Criterion.FirstOrDefault(y => y.Criterion.Name == ex.Criterion) != null
                                     &&
                                     (x.Product_Criterion.FirstOrDefault(y => y.Criterion.Name == ex.Criterion).Value).
                                         Contains(ex.Value));
                        break;
                    default:
                        throw new SearchException("Opérateur de critère invalide '" + ex.Operator + "'");
                }
            }
            catch(FormatException)
            {
                throw new SearchException("Valeur du critère '" + ex.Operator + "' invalide");
            }
            var replacer = new ParameterReplacer(expr.Parameters[0], _target);
            var newBody = replacer.Visit(expr.Body);
            _results.Push(newBody);
        }

        public void Process(SearchRequest ex)
        {
            _target = Expression.Parameter(typeof (AlmaDatabase.Product));
            ex.Expression.Accept(this);
            _result = Expression.Lambda<Func<Product, bool>>(_results.Pop(), _target);
        }

        public Expression<Func<Product, bool>> Result
        {
            get { return _result; }
        }
    }
}